/*
 * Searches using the history service for visits to the given URI
 *
 * Params:
 * args - object passed to the execute function
 */
function doHistorySearch(args) {
  // get URI from parameter
  var uri = makeNsIURI(args.object.text);
  if (uri == null) { // failed
    var errorMessage = _("Invalid URI! Enter valid URI.");
    displayMessage(errorMessage);
    CmdUtils.log(errorMessage);
    return;
  }     
    
  // get history service
  // https://developer.mozilla.org/en/nsINavHistoryService
  var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
                                 .getService(Components.interfaces.nsINavHistoryService);
    
  // create query
  // https://developer.mozilla.org/en/nsINavHistoryQuery
  var query = historyService.getNewQuery();
  query.uri = uri;
  query.uriIsPrefix = true;
    
  // https://developer.mozilla.org/en/nsINavHistoryQueryOptions
  var options = historyService.getNewQueryOptions();
  options.sortingMode = 3;
  options.resultType = 1;
    
  // execute query
  // https://developer.mozilla.org/en/nsINavHistoryResult
  var result = historyService.executeQuery(query, options);
    
  // process results
  // https://developer.mozilla.org/en/NsINavHistoryContainerResultNode
  var container = result.root;
  container.containerOpen = true;
    
  var html = "<h1>" + _("Search results:") + "</h1><h2>" +
    _("Visits for") + " " + uri.spec + "</h2>";
  if (container.childCount == 0) { // if there are no visits to the given URI
    html += "<h3>" + _("Sorry, there are no results for your request.") + "</h3>";
  } else {
    html += "<h3>" + container.childCount + " " + _("hits sorted by date") + "</h3>";
    html += "<table><thead><tr><th>" + _("Date") + "</th><th>" + _("URL") +
      "</th></tr><tbody>";
  
  var node, date;
    // walk through all visits
    for (var i = 0; i < container.childCount; i ++) {
      node = container.getChild(i);
    
      // https://developer.mozilla.org/en/Querying_Places#Using_the_results
      switch(node.type) {
    
        // https://developer.mozilla.org/en/nsINavHistoryResultNode
        case node.RESULT_TYPE_VISIT:
          // ignore microseconds
          date = new Date(node.time/1000);
          html += "<tr><td class=\"date\">" + date.toLocaleString() +
            "</td><td class=\"url\"><a href = \"" + node.uri + "\">" +
            node.uri + "</a></td></tr>";
          break;
      }
    }
    html += "</table>";
  }
  container.containerOpen = false;
    
  // display results
  openResultPage(html, _("Search results: visits for") + " " + uri.spec);
}

/*
 * Searches in bookmark descriptions
 *
 * Params:
 * args - object passed to the execute function
 */
function bookmarkDescriptionSearch(args) {
  
  // get annotation service
  // https://developer.mozilla.org/en/nsIAnnotationService
  var annotationService = Components.classes["@mozilla.org/browser/annotation-service;1"]
                                    .getService(Components.interfaces.nsIAnnotationService);

  // get bookmark service
  // https://developer.mozilla.org/en/nsINavBookmarksService
  var bookmarkService = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
                                  .getService(Components.interfaces.nsINavBookmarksService);

  var length = {};

  // get all items with an annotation named bookmarkProperties/description
  var res = annotationService.getItemsWithAnnotation("bookmarkProperties/description", length);
  var desc;
  var html = "";
  var count = 0;

  // get regular expression from parameter
  var regexp = new RegExp(args.object.text, "i");

  // walk through all retrieved items
  for each (var id in res) {

    // get annotation value
    desc = annotationService.getItemAnnotation(id, "bookmarkProperties/description");
    if (desc.match(regexp)) { // if annotation mathes the given regular expression

      // append result
      html += "<tr><td class=\"title\"><a href=\"" +
        bookmarkService.getBookmarkURI(id).spec + "\">" +
        bookmarkService.getItemTitle(id) + "</a></td><td class=\"desc\">" +
        desc + "</td></tr>";

      // increase result count
      count++;
    }
  }
  if (html.length == 0) { // if there are no matches
    html = "<h3>" + _("Sorry, there are no results for your request.") + "</h3>";
  } else {
    html = "<h3>" + count + " " + _("hits") + "</h3>" +
      "<table class=\"bookmark\"><thead><tr><th>" + _("Title") + "</th><th>" +
      _("Description") + "</th></tr><tbody>" + html;
  }
  html = "<h1>" + _("Search results:") + "</h1><h2>" + 
    _("Regular expression") + " " + regexp + "</h2>" + html;
  
  // display results
  openResultPage(html, _("Search results for expression") + " " + regexp);
}

/*
 * Lists all visited URLs using the history service
 */
function doHistoryListing() {
  // get history service
  // https://developer.mozilla.org/en/nsINavHistoryService
  var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
                                 .getService(Components.interfaces.nsINavHistoryService);

  // create query
  // https://developer.mozilla.org/en/nsINavHistoryQuery
  var query = historyService.getNewQuery();

  // https://developer.mozilla.org/en/nsINavHistoryQueryOptions
  var options = historyService.getNewQueryOptions();
  options.sortingMode = 5;
  options.resultType = 0
  options.queryType = 0;

  // execute query
  // https://developer.mozilla.org/en/nsINavHistoryResult
  var result = historyService.executeQuery(query, options);

  // process results
  // https://developer.mozilla.org/en/NsINavHistoryContainerResultNode
  var container = result.root;
  container.containerOpen = true;
    
  var html = "<h1>" + _("Search results:") + "</h1>";
  if (container.childCount == 0) { // if there are no history entries
    html += "<h3>" + _("Sorry, there are no results for your request.") + "</h3>";
  } else {
    html += "<h3>" + container.childCount + " " + _("URLs in ascending order") + "</h3>";
    html += "<table><thead><tr><th>" + _("URL") + "</th></tr><tbody>";
    
    var node;
    // walk through all visits
    for (var i = 0; i < container.childCount; i ++) {
      node = container.getChild(i);

      // https://developer.mozilla.org/en/Querying_Places#Using_the_results
      switch(node.type) {

        // https://developer.mozilla.org/en/nsINavHistoryResultNode
        case node.RESULT_TYPE_URI:
          html += "<tr><td class=\"urllist\"><a href = \"" + node.uri + "\">" +
            node.uri + "</a></td></tr>";
          break;
      }
    }
    html += "</table>";
  }
  container.containerOpen = false;

  // display results
  openResultPage(html, _("Search results: Visited URLs"));
}

/*
 * Displays results in a new tab
 *
 * Params:
 * aHtmlCode - preprocessed results
 * aPageTitle - page title for the new tab
 */
function openResultPage(aHtmlCode, aPageTitle) {

  // CSS code
  var style = (<r><![CDATA[

    body {
      background-color: #CADCEB;
      font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
    }
    h1 {
      font-size: x-large; color: #105289;
    }
    h2, h3 {
      font-size: large; color: #105289;
    }
    table {
      background-color: #E3E6EE;
      margin-left: auto;
      margin-right: auto;
      width: 100%;
      padding-top: 5px;
      padding-bottom: 5px;
      border: 1px solid #12A3EB;
      border-collapse: collapse;
    }
    a, a:visited {
      color: #105289 !important;
      text-decoration:none !important;
    }
    a:hover {
      color: #D31141 !important;
      text-decoration: underline !important;
    }
    tr {
      background-color: #E1EBF2;
    }
    tr:hover {
      background-color: #CFE1F6;
    }
    td {
      font-size: small; color: #536482;
    }
    th {
      font-size: small;
      color: #FFFFFF;
      background-color: #12A3EB;
    }
    td.date {
      width: 25%;
      padding-left: 5px;
    }
    td.url {
      width: 75%;
      padding-right: 5px;
    }
    td.urllist {
      width: 100%;
      padding-left: 10px;
      padding-right: 10px;
    }
    td.title {
      width: 35%;
      padding-left: 5px;
    }
    td.desc {
      width: 65%;
      padding-right: 5px;
    }
    table.bookmark td, table.bookmark th {
      border-bottom: 1px solid #12A3EB;
    }
    td.domain {
      width: 75%;
      padding-left: 5px;
    }
    td.visit {
      width: 25%;
      padding-right: 5px;
      text-align: center;
    }
    th.domainheader {
      text-align: left;
    }

    ]]></r>).toString();

  // append CSS before the preprocessed result code
  aHtmlCode = "<style>" + style + "</style>" + aHtmlCode;

  // get browser
  var browser = context.chromeWindow.getBrowser();
  // append new blank tab
  // not using focusUrlInBrowser to prevent interference with existing blank tabs
  // not using openUrlInBrowser because we need a reference to the new tab 
  var newTab = browser.addTab("about:blank");
  // focus on new tab
  browser.selectedTab = newTab;
    
  // This must be done to make generated content render
  // WebDeveloper Toolbar hack
  var request = new XMLHttpRequest();
  request.open("get", "about:blank", false);
  request.send(null);
    
  // getDocument now points to the generated blank tab
  doc = CmdUtils.getDocument();
  doc.body.innerHTML = aHtmlCode;
  doc.title = aPageTitle;
  doc.close();
}

/*
 * Takes a string and returns a nsIURI (null if the string isn't a valid URI).
 *
 * Params:
 * aURIString - string which represents an URI
 */
function makeNsIURI(aURIString) {

  // gets io service
  // https://developer.mozilla.org/en/nsIURI
  var ioService = Components.classes["@mozilla.org/network/io-service;1"]  
                            .getService(Components.interfaces.nsIIOService);
  var uri = null;
  try {
      // make nsIURI
      uri = ioService.newURI(aURIString, null, null);
  } catch (e) { // if the string isn't a valid URI
    //var errorMessage = _("Invalid URI!");
    //CmdUtils.log(errorMessage + " Exception: " + e);
  }
  return uri;
}

/*
 * Ubiquity command list history visits
 */
CmdUtils.CreateCommand({
  names: ["hisvis", "list history visits"],
  icon: "http://www.mozilla.org/images/mozilla-16.png",
  homepage: "http://boardraider-ubiquity-commands.googlecode.com/svn/trunk/placestools.html",
  author: {name: "boardraider"},
  license: "GPL 2 or newer",
  description: "Lists all visit dates for a given URI (can be a prefix).",
  arguments: [{role: "object", nountype: noun_arb_text, label: "uri"}],
  
  preview: function preview(pblock, args) {
    if (args.object.text.length == 0) {
      pblock.innerHTML = _("Enter URI to search for.");
      return;
    }
    var uri = makeNsIURI(args.object.text);
    if (uri == null) {    
      pblock.innerHTML = _("Invalid URI! Enter valid URI to search for.");
    } else {
      pblock.innerHTML = _("Press Enter to search for visits to") + " <b>" +
        uri.spec + "</b>. " +
        _("This could take some time depending on your request and your browsing habits. Please be patient.");
    }
  },

  execute: function execute(args) {
    doHistorySearch(args);
  }
});

/*
 * Ubiquity command bookmark description search
 */
CmdUtils.CreateCommand({
  names: ["bds", "bookmark description search"],
  icon: "http://www.mozilla.org/images/mozilla-16.png",
  homepage: "http://boardraider-ubiquity-commands.googlecode.com/svn/trunk/placestools.html",
  author: {name: "boardraider"},
  license: "GPL 2 or newer",
  description: "Searching in bookmark descriptions with regular expressions (case insensitive).",
  help: "Workaround for <a href=\"https://bugzilla.mozilla.org/show_bug.cgi?id=393584\">Bug 393584</a>",
  arguments: [{role: "object", nountype: noun_arb_text, label: "regular expression"}],

  preview: function preview(pblock, args) {
    if (args.object.text.length == 0) {
      pblock.innerHTML = _("Searching in bookmark descriptions. Enter regular expression.");
      return;
    }
    var regexp = new RegExp(args.object.text, "i");
    pblock.innerHTML = _("Press Enter to search in bookmark descriptions with the regular expression") +
      " <b>" + regexp + "</b>. " +
      _("This could take some time depending on the amount of your bookmarks. Please be patient.");
  },

  execute: function execute(args) {
    bookmarkDescriptionSearch(args);
  }
});

/*
 * Ubiquity command list history visits
 */
CmdUtils.CreateCommand({
  names: ["hislis", "list history urls"],
  icon: "http://www.mozilla.org/images/mozilla-16.png",
  homepage: "http://boardraider-ubiquity-commands.googlecode.com/svn/trunk/placestools.html",
  author: {name: "boardraider"},
  license: "GPL 2 or newer",
  description: "Lists all visited URLs",
  
  preview: function preview(pblock) {
    pblock.innerHTML = _("Press Enter to list all visited URLs.") + " " +
      _("This could take some time depending on the size of your history. Please be patient.");
  },

  execute: function execute() {
    doHistoryListing();
  }
});

/*
 * Ubiquity command fast bookmark this
 */
CmdUtils.CreateCommand({
  names: ["fbt", "fast bookmark this"],
  icon: "http://www.mozilla.org/images/mozilla-16.png",
  homepage: "http://boardraider-ubiquity-commands.googlecode.com/svn/trunk/placestools.html",
  author: {name: "boardraider"},
  license: "GPL 2 or newer",
  description: "Adds current page to the unsorteds bookmark list.",

  preview: function preview(pblock) {
    var doc = CmdUtils.getDocument();
    var uri = makeNsIURI(doc.location.href);
    if (uri == null) {
      pblock.innerHTML = _("Invalid URI! Sorry, can't bookmark this page.");
    } else {
      pblock.innerHTML = _("Press Enter to add") + " <b>" + uri.spec + "</b> " +
        _("to your list of unsorted bookmarks.");
    }
  },

  execute: function execute() {
    var doc = CmdUtils.getDocument();
    var uri = makeNsIURI(doc.location.href);
    if (uri == null) {
      var errorMessage = _("Invalid URI! Sorry, can't bookmark this page.");
      displayMessage(errorMessage);
      CmdUtils.log(errorMessage);
    } else {
      // get bookmark service
      // https://developer.mozilla.org/en/nsINavBookmarksService
      var bookmarkService = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
                                      .getService(Components.interfaces.nsINavBookmarksService);

      // append bookmark to the list of unsorted bookmarks
      bookmarkService.insertBookmark(bookmarkService.unfiledBookmarksFolder, uri, -1, doc.title);
      displayMessage(_("Bookmark added"));
    }
  }
});

/*
 * Custom noun type for folder suggestions
 *
 * Suggests folders from your bookmarks as you type.
 * Opens bookmarks, if a folder is found.
 */
var noun_type_folder_suggest = {

  label: "folder",
  folderList: [], // the current list of bookmark folders [[folderID, folderName], [folderID, folderName] ...]
  historyService: null, // history service
  bookmarksService: null, // bookmark service
  query: null, // query
  options: null, // options

  /*
   * Init members
   */
  init: function() {
    // https://developer.mozilla.org/en/nsINavHistoryService
    this.historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
                                    .getService(Components.interfaces.nsINavHistoryService);
    // https://developer.mozilla.org/en/nsINavBookmarksService
    this.bookmarksService = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"]
                                      .getService(Components.interfaces.nsINavBookmarksService);
    // https://developer.mozilla.org/en/nsINavHistoryQuery
    this.query = this.historyService.getNewQuery();
    // https://developer.mozilla.org/en/nsINavHistoryQueryOptions
    this.options = this.historyService.getNewQueryOptions();
  },

  /*
   * Refreshes the current folder list
   */
  refreshFolderList: function() {
    this.folderList = this.getAllBookmarkFolders();
  },

  /*
   * Creates list of bookmark folders
   */
  getAllBookmarkFolders: function() {
    if (!this.historyService) {
      this.init();
    }
    // get all folder IDs
    var folderIDList = iterateFolders(this.bookmarksService.bookmarksMenuFolder, this);
    folderIDList = folderIDList.concat(iterateFolders(this.bookmarksService.toolbarFolder, this));
    folderIDList = folderIDList.concat(iterateFolders(this.bookmarksService.unfiledBookmarksFolder, this));
    folderIDList = folderIDList.concat(iterateFolders(this.bookmarksService.tagsFolder, this));
    // create array [[folderID, folderName], [folderID, folderName] ...]
    var completeList = new Array();
    for (var i = 0; i < folderIDList.length; i ++) {
      completeList.push([folderIDList[i], this.bookmarksService.getItemTitle(folderIDList[i])]);
    }
    return completeList;

    /*
     * Recursively walks through the bookmarks tree
     *
     * Params:
     * rootID - ID of current folder node
     * ref - reference to the noun_type_folder_suggest object
     */
    function iterateFolders(rootID, ref) {
      var tempList = new Array();
      // append current folder
      tempList.push(rootID);
      // set current folder as root for the query (object defined outside)
      ref.query.setFolders([rootID], 1);
      // execute query, get https://developer.mozilla.org/en/nsINavHistoryResult
      var result = ref.historyService.executeQuery(ref.query, ref.options);
      // process results, get https://developer.mozilla.org/en/NsINavHistoryContainerResultNode
      var resultRootNode = result.root;
      resultRootNode.containerOpen = true;
      var node;
      for (var i = 0; i < resultRootNode.childCount; i ++) {
        node = resultRootNode.getChild(i);
        // https://developer.mozilla.org/en/Querying_Places#Using_the_results
        if (node.type == 6) { // if childnode is a folder
          // recursively add the sub folder with https://developer.mozilla.org/en/nsINavHistoryResultNode
          tempList = tempList.concat(iterateFolders(node.itemId, ref));
        }
      }
      resultRootNode.containerOpen = false;
      return tempList;
    }
  },
  
  /*
   * standard suggest function for the noun type
   */
  suggest: function(text, html, callback, selectionIndices) {
    var suggestions  = [];
    if (this.folderList.length == 0) { // if the list of current folder suggestions is empty
      this.refreshFolderList();
    }
    // add current input as suggestion in case of an outdated folder list
    suggestions.push(CmdUtils.makeSugg(text));
    for (var i = 0; i < this.folderList.length; i++) {
      if (this.folderList[i][1].match(text, "i")) { // if folder name matches input
        suggestions.push(CmdUtils.makeSugg(this.folderList[i][1]));
      }
    }
    return suggestions;
  },

  /*
   * Searches for folder name in folder list and in case gets all contained bookmarks. If there are more folders
   * with the same name bookmarks get appended.
   *
   * Params:
   * folderName - name of folder to search for
   */
  getBookmarkList: function(folderName) {
    if (!this.historyService) {
      this.init();
    }
    var bookmarkList = null;
    // ignore case
    var lowerName = folderName.toLowerCase();
    for (var i = 0; i < this.folderList.length; i++) {
      if (this.folderList[i][1].toLowerCase() == lowerName) { // if folder name found
        if (!bookmarkList) {
          var bookmarkList = new Array();
        }
        // set found folder as root for the query
        this.query.setFolders([this.folderList[i][0]], 1);
        // execute query, gets https://developer.mozilla.org/en/nsINavHistoryResult
        var result = this.historyService.executeQuery(this.query, this.options);
        // process results, gets https://developer.mozilla.org/en/NsINavHistoryContainerResultNode
        var resultRootNode = result.root;
        resultRootNode.containerOpen = true;
        var node;
        for (var j = 0; j < resultRootNode.childCount; j++) {
          node = resultRootNode.getChild(j);
          // https://developer.mozilla.org/en/Querying_Places#Using_the_results
          if (node.type == 0) { // if childnode is a bookmark
            // https://developer.mozilla.org/en/nsINavHistoryResultNode
            bookmarkList.push(node.uri);
          }
        }
        resultRootNode.containerOpen = false;
      }
    }
    return bookmarkList;
  },

  /*
   * Opens bookmarks from folders with the given name
   *
   * Params:
   * args - object passed to the execute function
   */
  openBookmarksFromFolder: function(args) {
    if (args.object.text.length == 0) { // without input
      var errorMessage = _("No folder name provided.");
      displayMessage(errorMessage);
      CmdUtils.log(errorMessage);
      return;
    }     
    // refresh current folder list for actual results
    this.refreshFolderList();
    // get bookmarks to open
    var bookmarkList = this.getBookmarkList(args.object.text);
    if (!bookmarkList) { // no folder found
      var errorMessage = _("No folder with name") + " " + args.object.text +
        " " + _("found.");
      displayMessage(errorMessage);
      CmdUtils.log(errorMessage);
      return;
    }
    if (bookmarkList.length == 0) { // if folder found, but empty
        var errorMessage = _("Folder with name") + " " + args.object.text +
        " " + _("found, but it's empty.");
      displayMessage(errorMessage);
      CmdUtils.log(errorMessage);
      return;
    }
    // open bookmarks
    for (var i = 0; i < bookmarkList.length; i++) {
      Utils.openUrlInBrowser(bookmarkList[i], null);
    }
  }
}

/*
 * Ubiquity command open bookmark folder
 */
CmdUtils.CreateCommand({
  names: ["obf", "open bookmark folder"],
  icon: "http://www.mozilla.org/images/mozilla-16.png",
  homepage: "http://boardraider-ubiquity-commands.googlecode.com/svn/trunk/placestools.html",
  author: {name: "boardraider"},
  license: "GPL 2 or newer",
  description: "Opens all bookmarks in a folder.",
  arguments: [{role: "object", nountype: noun_type_folder_suggest, label: "folder name"}],

  preview: function preview(pblock, args) {
    if (args.object.text.length == 0) { // without input
      pblock.innerHTML = _("Enter folder name.");
    } else { // with input
      pblock.innerHTML = _("Press Enter to open all bookmarks in your folder") +
        " <b>" + args.object.text + "</b> " + _("in new tabs.");
    }
  },

  execute: function execute(args) {
    noun_type_folder_suggest.openBookmarksFromFolder(args);
  }
});

/*
 * Custom noun type for domain type suggestions
 */
var noun_type_domainType = {
  label: "domain type",

  types: ["dom", "sub", "tld"],
  
  suggest: function(text, html, callback, selectionIndices) {
    var suggestions  = [];
    for (var i = 0; i < this.types.length; i++) {
        suggestions.push(CmdUtils.makeSugg(this.types[i]));
    }
    return suggestions;
  }
}

/*
 * Ubiquity command visdom
 */
CmdUtils.CreateCommand({
  names: ["visdom", "visits to domain", "wastedmytimeon"],
  icon: "http://www.mozilla.org/images/mozilla-16.png",
  homepage: "http://boardraider-ubiquity-commands.googlecode.com/svn/trunk/placestools.html",
  author: {name: "boardraider"},
  license: "GPL 2 or newer",
  description: "Lists your visit counts on top-level domains, domains or subdomains in descending order.",

  arguments: [{role: "object", nountype: noun_type_domainType, label: "domain type"}],
  preview: function preview(pblock, domainType) {
    var time = _(" This could take some time depending on the size of your history. Please be patient.");
    var pre = _("Press Enter to list your visit counts on ");
    var post = _(" in descending order.");
    var dom = _("domains");
    if (domainType.object.text.length > 0) { // with input
      var domString;
      switch (domainType.object.text) {
        case "tld":
          dom = _("top-level domains");
          break;
        case "sub":
          dom = _("subdomains");
          break;
        default:
        case "dom":
          break;
      } 
    }
    pblock.innerHTML =  pre + "<b>" + dom + "</b>" + post + time;
  },

  execute: function execute(domainType) {

    // https://developer.mozilla.org/en/nsIEffectiveTLDService
    var eTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"]
                                .getService(Components.interfaces.nsIEffectiveTLDService);

    // https://developer.mozilla.org/en/nsINavHistoryService
    var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
                                   .getService(Components.interfaces.nsINavHistoryService);

    // create query
    // https://developer.mozilla.org/en/nsINavHistoryQuery
    var query = historyService.getNewQuery();
    query.minVisits = 1;

    // https://developer.mozilla.org/en/nsINavHistoryQueryOptions
    var options = historyService.getNewQueryOptions();
    options.sortingMode = 8;
    options.resultType = 0;

    // execute query
    // https://developer.mozilla.org/en/nsINavHistoryResult
    var result = historyService.executeQuery(query, options);

    // process results
    // https://developer.mozilla.org/en/NsINavHistoryContainerResultNode
    var container = result.root;
    container.containerOpen = true;
    
    var domString = _("domains");
    var headerString = _("Domain");
    switch (domainType.object.text) {
      case "tld":
        domString = _("top-level domains");
        headerString = _("Top-level domain");
        break;
      case "sub":
        domString = _("subdomains");
        headerString = _("Subdomain");
        break;
      default:
      case "dom":
        break;
    } 

    var html = "<h1>" + _("Search results:") + "</h1><h2>" +
      _("Visit counts for ") + domString + _(" in descending order") + "</h2>";
    if (container.childCount == 0) { // if there is a empty history
      html +=  "<h3>" + _("Sorry, your history is empty.") + "</h3>";
    } else {
      html += "<table><thead><tr><th class=\"domainheader\">" + headerString +
        "</th><th>" + _("Visit count") + "</th></tr><tbody>";
      var node, uri, domPart;
      var aList = new Object(); // used as associative array
      // walk through all results
      for (var i = 0; i < container.childCount; i ++) {
        node = container.getChild(i);
        // https://developer.mozilla.org/en/Querying_Places#Using_the_results
        switch(node.type) {
          // https://developer.mozilla.org/en/nsINavHistoryResultNode
          case node.RESULT_TYPE_URI:
            try {
              uri = makeNsIURI(node.uri);
              if (uri && uri.host && uri.host.length > 0) {
                switch(domainType.object.text) {
                  case "tld":
                    try { 
                      // https://developer.mozilla.org/en/Code_snippets/URI_parsing
                      domPart = eTLDService.getPublicSuffix(uri);
                    } catch(e) { // add IPs
                      domPart = uri.host;
                    }
                    break;
                  case "sub":
                    domPart = uri.host;
                    break;
                  default:
                  case "dom":
                    try {
                      // https://developer.mozilla.org/en/Code_snippets/URI_parsing 
                      domPart = eTLDService.getBaseDomain(uri, 0);
                    } catch(e) { // add IPs and localhost 
                      domPart = uri.host;
                    }
                    break;
                } 
                if (aList[domPart]) { // if entry exists increase counter
                  aList[domPart] = aList[domPart] + 1;
                } else { // add new entry
                  aList[domPart] = 1;
                }
              }
            } catch(e) { // catch execptions for problematic URLs and ignore, p.e. jar:
            }
          break;
        }
      }
      var arr = new Array(); // array of domain parts
      var count = 0; // counter
      for (var dom in aList) { // for each entry in associative array
        arr[count] = {domain: dom, vcount: aList[dom]};
        count++;
      }

      /*
       * Trivial sort function (descending order)
       */
      function arrSort(a, b) {
         return b.vcount - a.vcount;
      }
  
      // sort array
      arr.sort(arrSort);
      // get HTML code
      for (var i = 0; i < arr.length; i++) {
        html += "<tr><td class=\"domain\">" + arr[i].domain +
          "</td><td class=\"visit\">" + arr[i].vcount + "</td></tr>";
      }
      html += "</table>";
    }
    container.containerOpen = false;

    // display results
    openResultPage(html, _("Search results: visit counts for") + " " + domString);
  }
});